/*
 * Copyright (C) 2010 Interactive Media Management
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.i2m.converge.ejb.facades;

import dk.i2m.converge.core.search.SearchEngineIndexingException;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
import com.adobe.xmp.XMPMetaFactory;
import com.adobe.xmp.properties.XMPProperty;
import dk.i2m.commons.FileUtils;
import dk.i2m.commons.ImageUtils;
import dk.i2m.converge.core.search.CannotIndexException;
import dk.i2m.converge.core.content.MediaItem;
import dk.i2m.converge.core.content.MediaItemStatus;
import dk.i2m.converge.core.content.MediaItemVersionLabel;
import dk.i2m.converge.core.content.MediaRepository;
import dk.i2m.converge.core.content.NewsItemMediaAttachment;
import dk.i2m.converge.core.security.UserAccount;
import dk.i2m.converge.ejb.services.DaoServiceLocal;
import dk.i2m.converge.ejb.services.InvalidMediaRepositoryException;
import dk.i2m.converge.ejb.services.MediaRepositoryIndexingException;
import dk.i2m.converge.ejb.services.QueryBuilder;
import dk.i2m.converge.ejb.services.DataNotFoundException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.formats.tiff.TiffImageMetadata;
import org.apache.sanselan.formats.tiff.TiffImageMetadata.Item;

/**
 * Stateless enterprise java bean providing a facade for interacting with the
 * media database.
 *
 * @author Allan Lykke Christensen
 */
@Stateless
public class MediaDatabaseFacadeBean implements MediaDatabaseFacadeLocal {

    private static final Logger logger = Logger.getLogger(MediaDatabaseFacadeBean.class.getName());

    @EJB private DaoServiceLocal daoService;

    @EJB private SearchEngineLocal searchEngineService;

    /**{@inheritDoc } */
    @Override
    public MediaRepository create(MediaRepository mediaRepository) {
        return daoService.create(mediaRepository);
    }

    /**{@inheritDoc } */
    @Override
    public List<MediaRepository> findAllMediaRepositories() {
        return daoService.findAll(MediaRepository.class);
    }

    /**{@inheritDoc } */
    @Override
    public List<MediaRepository> findWritableMediaRepositories() {
        return daoService.findWithNamedQuery(MediaRepository.FIND_WRITABLE);
    }

    /**{@inheritDoc } */
    @Override
    public MediaRepository findMediaRepositoryById(Long id) throws DataNotFoundException {
        return daoService.findById(MediaRepository.class, id);
    }

    /**{@inheritDoc } */
    @Override
    public MediaRepository update(MediaRepository mediaRepository) {
        return daoService.update(mediaRepository);
    }

    /**{@inheritDoc } */
    @Override
    public void deleteMediaRepositoryById(Long id) throws DataNotFoundException {
        daoService.delete(MediaRepository.class, id);
    }

    /**{@inheritDoc } */
    @Override
    public void indexMediaRepositories() throws InvalidMediaRepositoryException, MediaRepositoryIndexingException {
        Map<String, Object> parameters = QueryBuilder.with("status", MediaItemStatus.APPROVED).parameters();
        List<MediaItem> items = daoService.findWithNamedQuery(MediaItem.FIND_BY_STATUS, parameters);

        for (MediaItem item : items) {
            try {
                searchEngineService.index(item);
            } catch (SearchEngineIndexingException ex) {
                logger.log(Level.SEVERE, ex.getMessage());
                logger.log(Level.FINE, "", ex);
            }
        }
    }

    /** {@inheritDoc } */
    @Override
    public List<MediaItemVersionLabel> findMediaItemVersionLabels() {
        return daoService.findAll(MediaItemVersionLabel.class);
    }

    /** {@inheritDoc } */
    @Override
    public MediaItemVersionLabel findMediaItemVersionLabelById(Long id) throws DataNotFoundException {
        return daoService.findById(MediaItemVersionLabel.class, id);
    }

    /** {@inheritDoc } */
    @Override
    public MediaItemVersionLabel create(MediaItemVersionLabel label) {
        return daoService.create(label);
    }

    /** {@inheritDoc } */
    @Override
    public MediaItemVersionLabel update(MediaItemVersionLabel label) {
        return daoService.update(label);
    }

    /** {@inheritDoc } */
    @Override
    public void deleteMediaItemVersionLabel(Long id) {
        daoService.delete(MediaItemVersionLabel.class, id);
    }

    /** {@inheritDoc } */
    @Override
    public MediaItem create(MediaItem mediaItem) {
        mediaItem.setCreated(Calendar.getInstance());
        mediaItem.setUpdated(mediaItem.getCreated());
        return daoService.create(mediaItem);
    }

    /** {@inheritDoc } */
    @Override
    public MediaItem update(MediaItem mediaItem) {
        mediaItem.setUpdated(Calendar.getInstance());
        mediaItem = daoService.update(mediaItem);
        try {
            searchEngineService.index(mediaItem);
        } catch (SearchEngineIndexingException ex) {
            Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
        }

        return mediaItem;
    }

    /** {@inheritDoc } */
    @Override
    public void deleteMediaItemById(Long id) {
        try {
            MediaItem mi = findMediaItemById(id);
            try {
                searchEngineService.removeFromIndex(mi);
            } catch (SearchEngineIndexingException ex) {
                logger.log(Level.SEVERE, "Could not remove MediaItem #{0} from search engine index. {1}", new Object[]{id, ex.getMessage()});
                logger.log(Level.FINE, "", ex);
            }
            daoService.delete(MediaItem.class, id);
        } catch (DataNotFoundException ex) {
            logger.log(Level.FINE, "Could not delete non-existing MediaItem #{0}", id);
        }
    }

    @Override
    public MediaItem findMediaItemById(Long id) throws DataNotFoundException {
        return daoService.findById(MediaItem.class, id);
    }

    @Override
    public List<MediaItem> findMediaItemsByStatus(MediaItemStatus status) {
        Map<String, Object> params = QueryBuilder.with("status", status).parameters();
        return daoService.findWithNamedQuery(MediaItem.FIND_BY_STATUS, params);
    }

    @Override
    public List<MediaItem> findMediaItemsByOwner(UserAccount owner) {
        Map<String, Object> params = QueryBuilder.with("owner", owner).parameters();
        return daoService.findWithNamedQuery(MediaItem.FIND_BY_OWNER, params);
    }

    @Override
    public List<MediaItem> findCurrentMediaItems(UserAccount user, Long mediaRepositoryId) {
        try {
            MediaRepository mr = daoService.findById(MediaRepository.class, mediaRepositoryId);
            Map<String, Object> params = QueryBuilder.with("user", user).and("mediaRepository", mr).parameters();

            List<MediaItem> items = new ArrayList<MediaItem>();

            items.addAll(daoService.findWithNamedQuery(MediaItem.FIND_CURRENT_AS_OWNER, params));
            items.addAll(daoService.findWithNamedQuery(MediaItem.FIND_CURRENT_AS_EDITOR, params));

            Set set = new HashSet(items);
            return new ArrayList(set);
        } catch (DataNotFoundException ex) {
            return Collections.EMPTY_LIST;
        }
    }

    @Override
    public List<MediaItem> findCurrentMediaItems(UserAccount user, MediaItemStatus status, Long mediaRepositoryId) {
        try {
            MediaRepository mr = daoService.findById(MediaRepository.class, mediaRepositoryId);
            Map<String, Object> params = QueryBuilder.with("user", user).and("status", status).and("mediaRepository", mr).parameters();
            return daoService.findWithNamedQuery(MediaItem.FIND_BY_OWNER_AND_STATUS, params);
        } catch (DataNotFoundException ex) {
            return Collections.EMPTY_LIST;
        }
    }

    @Override
    public Map<String, String> store(byte[] file, MediaItem item) {

        String repositoryLocation = item.getMediaRepository().getLocation();

        FileOutputStream fos = null;
        try {


            File dir = new File(repositoryLocation);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            fos = new FileOutputStream(repositoryLocation + "/" + item.getId() + "-" + item.getFilename());
            fos.write(file);


        } catch (FileNotFoundException ex) {
            Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                fos.close();
            } catch (IOException ex) {
                Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        // Detect if a thumb should be generated
        if (item.isThumbAvailable()) {
            try {
                byte[] thumb = ImageUtils.generateThumbnail(file, 150, 150, 100);
                FileUtils.writeToFile(thumb, repositoryLocation + "/" + item.getId() + "-" + "150x150" + item.getFilename());
            } catch (InterruptedException ex) {
                Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        try {
            return indexXmp(file);
        } catch (CannotIndexException ex) {
            Logger.getLogger(MediaDatabaseFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
            return Collections.EMPTY_MAP;
        }
    }

    /** Namespace of the dublin core. */
    private String NS_DC = "http://purl.org/dc/elements/1.1/";

    /** Namespace of photoshop tags. */
    private String NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/";

    private Map<String, String> indexXmp(byte[] file) throws CannotIndexException {
        Map<String, String> properties = new HashMap<String, String>();
        String xml;
        try {
            xml = Sanselan.getXmpXml(file);

            if (xml == null) {
                try {
                    IImageMetadata meta = Sanselan.getMetadata(file);

                    if (meta != null) {
                        ArrayList items = meta.getItems();
                        for (int i = 0; i < items.size(); i++) {
                            try {
                                TiffImageMetadata.Item item = (Item) items.get(i);
                                properties.put(item.getTiffField().getTagName(), "" + item.getTiffField().getValue());
                            } catch (Exception ex) {
                                logger.log(Level.INFO, ex.getMessage());
                                logger.log(Level.FINE, "", ex);
                            }
                        }
                    }
                } catch (ImageReadException ex) {
                    logger.log(Level.INFO, "Image reading exception", ex.getMessage());
                    logger.log(Level.FINE, "", ex);
                } catch (IOException ex) {
                    logger.log(Level.SEVERE, "", ex);
                }
            } else {

                XMPMeta xmpMeta = XMPMetaFactory.parseFromString(xml);

                if (xmpMeta.doesPropertyExist(NS_PHOTOSHOP, "Headline")) {
                    XMPProperty headlineProperty = xmpMeta.getProperty(NS_PHOTOSHOP, "Headline");
                    properties.put("headline", ((String) headlineProperty.getValue()).trim());
                }

                if (xmpMeta.doesArrayItemExist(NS_DC, "description", 1)) {
                    XMPProperty descriptionProperty = xmpMeta.getArrayItem(NS_DC, "description", 1);
                    properties.put("description", ((String) descriptionProperty.getValue()).trim());
                }

                if (xmpMeta.doesArrayItemExist(NS_DC, "title", 1)) {
                    XMPProperty titleProperty = xmpMeta.getArrayItem(NS_DC, "title", 1);
                    properties.put("title", ((String) titleProperty.getValue()).trim());
                }

                int subjectCount = xmpMeta.countArrayItems(NS_DC, "subject");
                if (subjectCount > 0) {

                    for (int i = 1; i <= subjectCount; i++) {
                        XMPProperty subjectProperty = xmpMeta.getArrayItem(NS_DC, "subject", i);
                        properties.put("subject-" + i, ((String) subjectProperty.getValue()).trim());
                    }
                }
            }

        } catch (XMPException ex) {
            logger.log(Level.SEVERE, "", ex);
        } catch (ImageReadException ex) {
            logger.log(Level.FINE, "Image reading exception from file", ex);
        } catch (IOException ex) {
            logger.log(Level.FINE, "File reading exception from file", ex);
        }

        return properties;
    }

    /** {@inheritDoc } */
    @Override
    public boolean isMediaItemUsed(Long id) {
        try {
            MediaItem mediaItem = daoService.findById(MediaItem.class, id);
            Map<String, Object> params = QueryBuilder.with("mediaItem", mediaItem).parameters();
            List results = daoService.findWithNamedQuery(NewsItemMediaAttachment.FIND_BY_MEDIA_ITEM, params);

            if (results.isEmpty()) {
                return false;
            } else {
                return true;
            }

        } catch (DataNotFoundException ex) {
            return false;
        }
    }
}
